//           Copyright Daniel Goertzen 2007.
//  Distributed under the Boost Software License, Version 1.0.
//     (See accompanying file LICENSE_1_0.txt or copy at
//           http://www.boost.org/LICENSE_1_0.txt)

#ifndef EIPP_ET_H
#define EIPP_ET_H

#include <boost/preprocessor/list/for_each.hpp>
#include <boost/preprocessor/list/append.hpp>
#include <boost/preprocessor/list/transform.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/tuple/to_list.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/repetition/repeat_from_to.hpp>

#include <boost/shared_ptr.hpp>

namespace eipp
{
	using namespace boost;
	using namespace std;


	class bad_match : public std::exception
	{
	public:
		bad_match():std::exception()
			{}
		bad_match(const std::string &_w):std::exception()
			{ w=_w; }

		virtual ~bad_match() throw() {} 
		std::string w;
		const char *what() const throw() {return w.c_str();}
	};


//
// The types that et can contain...
//

// ... ones not requiring forward declarations
#define ET_TYPES_NOFORWARD						\
	BOOST_PP_TUPLE_TO_LIST(						\
		10, (									\
			int8_t, uint8_t,					\
			int16_t, uint16_t,					\
			int32_t, uint32_t,					\
			int64_t, uint64_t,					\
			float, double						\
			))

// ... and ones requiring forward declarations
#define ET_TYPES_FORWARD						\
	BOOST_PP_TUPLE_TO_LIST(						\
		5, (									\
			atom_t,								\
			string_t,							\
			binary_t,							\
			list_t,								\
			tuple_t								\
			))

#define ET_TYPES BOOST_PP_LIST_APPEND(ET_TYPES_NOFORWARD, ET_TYPES_FORWARD)

//
// utility macros
//

// forward declaration
#define FORWARD_DECLARE(_r,_data,elem) class elem;
// generate holder type name given a type
#define HOLDER_NAME(_d,_data,elem) BOOST_PP_CAT(holder_,elem)
// generate getter method name given a type
#define GETTER_NAME(_d,_data,elem) BOOST_PP_CAT(get_,elem)

// do forward declarations
	BOOST_PP_LIST_FOR_EACH(FORWARD_DECLARE, _, ET_TYPES_FORWARD)
	class encoded_t;

	class et
	{

	public:

        // forward declarations for all holder classes
		BOOST_PP_LIST_FOR_EACH(FORWARD_DECLARE, _,						\
							   BOOST_PP_LIST_TRANSFORM(HOLDER_NAME, _, ET_TYPES))

		// "special holder" forward decl
		class holder_encoded_t;




// base class for all holders

		struct holder_base_t
		{

// Generate default get_XXX_t method.  All these do is throw.
#define	ET_BASE_GETTER(_r,_data,elem)									\
			virtual elem & BOOST_PP_CAT(get_,elem)(et *)				\
				{ throw bad_match("not a " BOOST_PP_STRINGIZE(elem) ); } 
			
			BOOST_PP_LIST_FOR_EACH(ET_BASE_GETTER, _, ET_TYPES)
			
			virtual void ostream_insert(ostream &)=0;
		};


	protected:
 		mutable shared_ptr<holder_base_t> ptr;


 	public:


// Generate cast operators
// should have const and non-const versions??
#define ET_CAST(_r,_data,elem)											\
		operator elem & () { return ptr->GETTER_NAME(_,_,elem) (this); } \
		operator const elem & () const { return ptr->GETTER_NAME(_,_,elem) (const_cast<et*>(this)); } \

		BOOST_PP_LIST_FOR_EACH(ET_CAST, _, ET_TYPES)
#undef ET_CAST

		// Generate ctor declarations
#define ET_CTOR(_r,_data,elem)					\
		et(const elem &a);
		
		BOOST_PP_LIST_FOR_EACH(ET_CTOR, _, ET_TYPES)
#undef ET_CTOR


		// special case constructors
 		et() {}
		et(holder_base_t *p):ptr(p) {}
 		et(const char *s);

 		et(const encoded_t &);
// 			:ptr(new holder_decoded_t(ud){}

		void ostream_insert(ostream &ss) const;

	}; // class et

	ostream &operator <<(ostream &ss, const et &e)
	{
		e.ostream_insert(ss);
		return ss;
	}

}//namespace



// et is now fully defined, therefore the forward declared types (some
// of which contain et instances) can now be fully defined:
#include "types.hpp"


// Now that all types are fully defined, the encoder/decoder functions can be defined
#include "xcode.hpp"


// Now that et, all types, encoders/decoders are fully defined, the holders can be defined.
namespace eipp
{

#define ET_HOLDER(r,xxdata,elem)									\
	struct et::HOLDER_NAME(_,_,elem): public et::holder_base_t		\
	{																\
		elem data;													\
		HOLDER_NAME(_,_,elem)(){}									\
		HOLDER_NAME(_,_,elem)(const elem & _data): data(_data){}	\
		virtual elem &GETTER_NAME(_,_,elem)(et *){return data;}		\
		virtual void ostream_insert(ostream &ss){ss << data;}		\
	}; 
	// generate holder classes for all accepted types
	BOOST_PP_LIST_FOR_EACH(ET_HOLDER, _, ET_TYPES)
#undef ET_HOLDER


	// Generate ctors
#define ET_CTOR(_r,_data,elem)										\
	et::et(const elem &a): ptr(new HOLDER_NAME(_,_,elem) (a)) {}
		
	BOOST_PP_LIST_FOR_EACH(ET_CTOR, _, ET_TYPES)
#undef ET_CTOR

	et::et(const char *s):ptr(new holder_atom_t(s)) {}



// Generate get method for class holder_decoded
//
// The decoded holder gets replaced with a normal holder, and then
// that holder is used.
//
#define	ET_ENCODED_GETTER(_r,_xxdata,elem)								\
	virtual elem & BOOST_PP_CAT(get_,elem)(et *p_et)					\
	{																	\
 		shared_ptr<holder_base_t> newholder(new HOLDER_NAME(_,_,elem)); \
		decode( newholder->GETTER_NAME(_,_,elem)(p_et), data, data.cur ); \
		p_et->ptr=newholder;											\
		return (*p_et);													\
	};
	

	struct et::holder_encoded_t: public et::holder_base_t
	{
		encoded_t data;
		holder_encoded_t(encoded_t _data):
			data(_data){}

// 			virtual int encode(ei_x_buff *buf)
// 				{
// 					return ei_x_append_buf(buf,b,len);
// 				}

		// Generate all get operations for holder_decoded
		BOOST_PP_LIST_FOR_EACH(ET_ENCODED_GETTER, _, ET_TYPES)

		virtual void ostream_insert(ostream &ss){ss << "(encoded et)";}		\
	};

	et::et(const encoded_t &ud):ptr(new holder_encoded_t(ud)){}



// preproc cleanup
#undef ET_BASE_GETTER
#undef ET_ENCODED_GETTER
#undef ET_HOLDER
#undef ET_CAST
#undef ET_CTOR


	void et::ostream_insert(ostream &ss) const
	{
		BOOST_AUTO(p,ptr.get());
		if(p)
			p->ostream_insert(ss);
		else
			ss << "(uninitialized)";
	}




// named constructors
#define ET_ASSIGN(z,n,data) (*(i++))=BOOST_PP_CAT(a,n);
#define ET_NAMED_CTOR(container,arity)									\
	inline BOOST_PP_CAT(container,_t) container(BOOST_PP_ENUM_PARAMS(arity,const et &a)) \
	{																	\
		et e(new BOOST_PP_CAT(et::holder_,BOOST_PP_CAT(container,_t))()); \
		BOOST_PP_CAT(container,_t) &p(e);								\
		p.resize(arity);												\
		BOOST_AUTO(i,p.begin());										\
		BOOST_PP_REPEAT(arity,ET_ASSIGN,_)								\
			return e;													\
	}
	

#define CTOR_FUN(z,n,data) ET_NAMED_CTOR(data,n)
	BOOST_PP_REPEAT(15,CTOR_FUN,tuple) 
	BOOST_PP_REPEAT(15,CTOR_FUN,list) 

#undef CTOR_FUN
#undef ET_ASSIGN
#undef ET_NAMED_CTOR


#define ET_BINDER_CTOR_ARGS(z,n,data) T ## n  _t ## n
#define ET_BINDER_CTOR_INIT(z,n,data) t ## n (_t ## n)
#define ET_BINDER_VARS(z,n,data) T ## n  t ## n;
#define ET_BINDER_ASSIGN(z,n,data) t ## n =(*(i++));	
#define ET_UN_BINDER_ARGS(z,n,data) T ## n &
#define ET_UN_BINDER_PARAMS(z,n,data) T ## n & t ## n
#define ET_BINDER(container,arity)								  \
	template <BOOST_PP_ENUM_PARAMS(arity,typename T)>			  \
	class container ## binder ## arity							  \
	{															  \
	public:														  \
		container ## binder ## arity							  \
			(BOOST_PP_ENUM(arity,ET_BINDER_CTOR_ARGS,_)):		  \
			BOOST_PP_ENUM(arity,ET_BINDER_CTOR_INIT,_) {}		  \
		BOOST_PP_REPEAT(arity,ET_BINDER_VARS,_)					  \
		void operator =(const container ## _t &seq)				  \
			{													  \
				if(arity!=seq.size()) throw bad_match("wrong arity");	\
				BOOST_AUTO(i,seq.begin());						  \
				BOOST_PP_REPEAT(arity,ET_BINDER_ASSIGN,_)		  \
			}													  \
	};															  \
	template<BOOST_PP_ENUM_PARAMS(arity,typename T)>		  \
	container ## binder ## arity							  \
	<BOOST_PP_ENUM(arity,ET_UN_BINDER_ARGS,_)>				  \
	un ## container											  \
	(BOOST_PP_ENUM(arity,ET_UN_BINDER_PARAMS,_))				  \
	{															  \
		return container ## binder ## arity						  \
			<BOOST_PP_ENUM(arity,ET_UN_BINDER_ARGS,_)>			  \
			(BOOST_PP_ENUM_PARAMS(arity,t));					  \
	}

#define BINDER_FUN(z,n,data) ET_BINDER(data,n)
	BOOST_PP_REPEAT_FROM_TO(1,15,BINDER_FUN,tuple) 
	BOOST_PP_REPEAT_FROM_TO(1,15,BINDER_FUN,list) 


	void binary_to_term(et &dst,const et &src)
	{
	}

	et binary_to_term(const et &src)
	{
		et dst;
		binary_to_term(dst,src);
		return dst;
	}

}//namespace

#endif
